#!/usr/bin/env python
from __future__ import with_statement

import sys, re

import logging

import BeautifulSoup

import PyV8

class abstractmethod(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwds):
        raise NotImplementedError("method %s is abstract." % self.func.func_name)

class DOMException(RuntimeError, PyV8.JSClass):
    def __init__(self, code):
        self.code = code

    INDEX_SIZE_ERR                 = 1  # If index or size is negative, or greater than the allowed value
    DOMSTRING_SIZE_ERR             = 2  # If the specified range of text does not fit into a DOMString
    HIERARCHY_REQUEST_ERR          = 3  # If any node is inserted somewhere it doesn't belong
    WRONG_DOCUMENT_ERR             = 4  # If a node is used in a different document than the one that created it (that doesn't support it)
    INVALID_CHARACTER_ERR          = 5  # If an invalid or illegal character is specified, such as in a name.
    NO_DATA_ALLOWED_ERR            = 6  # If data is specified for a node which does not support data
    NO_MODIFICATION_ALLOWED_ERR    = 7  # If an attempt is made to modify an object where modifications are not allowed
    NOT_FOUND_ERR                  = 8  # If an attempt is made to reference a node in a context where it does not exist
    NOT_SUPPORTED_ERR              = 9  # If the implementation does not support the type of object requested
    INUSE_ATTRIBUTE_ERR            = 10 # If an attempt is made to add an attribute that is already in use elsewhere

class Node(PyV8.JSClass):
    # NodeType
    ELEMENT_NODE                   = 1
    ATTRIBUTE_NODE                 = 2
    TEXT_NODE                      = 3
    CDATA_SECTION_NODE             = 4
    ENTITY_REFERENCE_NODE          = 5
    ENTITY_NODE                    = 6
    PROCESSING_INSTRUCTION_NODE    = 7
    COMMENT_NODE                   = 8
    DOCUMENT_NODE                  = 9
    DOCUMENT_TYPE_NODE             = 10
    DOCUMENT_FRAGMENT_NODE         = 11
    NOTATION_NODE                  = 12

    def __init__(self, doc):
        self.doc = doc

    def __repr__(self):
        return "<Node %s at 0x%08X>" % (self.nodeName, id(self))

    def __eq__(self, other):
        return hasattr(other, "doc") and self.doc == other.doc

    def __ne__(self, other):
        return not self.__eq__(other)

    @property
    @abstractmethod
    def nodeType(self):
        pass

    @property
    @abstractmethod
    def nodeName(self):
        pass

    @abstractmethod
    def getNodeValue(self):
        return None

    @abstractmethod
    def setNodeValue(self, value):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

    nodeValue = property(getNodeValue, setNodeValue)

    @property
    def attributes(self):
        return None

    @property
    def childNodes(self):
        return NodeList(self.doc, [])

    @property
    def firstChild(self):
        return None

    @property
    def lastChild(self):
        return None

    @property
    def nextSibling(self):
        return None

    @property
    def previousSibling(self):
        return None

    @property
    def parentNode(self):
        return None

    @property
    def ownerDocument(self):
        return self.doc

    def insertBefore(self, newChild, refChild):
        raise DOMException(DOMException.HIERARCHY_REQUEST_ERR)

    def replaceChild(self, newChild, oldChild):
        raise DOMException(DOMException.HIERARCHY_REQUEST_ERR)

    def removeChild(self, oldChild):
        raise DOMException(DOMException.NOT_FOUND_ERR)

    def appendChild(self, newChild):
        raise DOMException(DOMException.HIERARCHY_REQUEST_ERR)

    def hasChildNodes(self):
        return False

    @abstractmethod
    def cloneNode(self, deep):
        pass

    @staticmethod
    def wrap(doc, obj):
        if obj is None:
            return None

        if type(obj) == BeautifulSoup.CData:
            return CDATASection(doc, obj)

        if type(obj) == BeautifulSoup.NavigableString:
            return Text(doc, obj)

        return Element(doc, obj)

class NodeList(PyV8.JSClass):
    def __init__(self, doc, nodes):
        self.doc = doc
        self.nodes = nodes

    def __len__(self):
        return self.length

    def __getitem__(self, key):
        return self.item(int(key))

    def item(self, index):
        return DOMImplementation.createHTMLElement(self.doc, self.nodes[index]) if 0 <= index and index < len(self.nodes) else None

    @property
    def length(self):
        return len(self.nodes)

class NamedNodeMap(PyV8.JSClass):
    def __init__(self, parent):
        self.parent = parent

    def getNamedItem(self, name):
        return self.parent.getAttributeNode(name)

    def setNamedItem(self, attr):
        oldattr = self.parent.getAttributeNode(attr.name)

        attr.parent = self.parent

        self.parent.tag[attr.name] = attr.value

        if oldattr:
            oldattr.parent = None

        return oldattr

    def removeNamedItem(self, name):
        self.parent.removeAttribute(name)

    def item(self, index):
        names = self.parent.tag.attrMap.keys()
        return self.parent.getAttributeNode(names[index]) if 0 <= index and index < len(names) else None

    @property
    def length(self):
        return len(self.parent.tag._getAttrMap())

class Attr(Node):
    _value = ""

    def __init__(self, parent, attr):
        self.parent = parent
        self.attr = attr

        self._value = self.getValue()

    def __repr__(self):
        return "<Attr object %s%s at 0x%08X>" % ("%s." % self.parent.tagName if self.parent else "", self.attr, id(self))

    def __eq__(self, other):
        return hasattr(other, "parent") and self.parent == other.parent and \
               hasattr(other, "attr") and self.attr == other.attr

    @property
    def nodeType(self):
        return Node.ATTRIBUTE_NODE

    @property
    def nodeName(self):
        return self.attr

    def getNodeValue(self):
        return self.getValue()

    def setNodeValue(self, value):
        return self.setValue(value)

    nodeValue = property(getNodeValue, setNodeValue)

    @property
    def childNodes(self):
        return NodeList(self.parent.doc, [])

    @property
    def parentNode(self):
        return self.parent

    @property
    def ownerDocument(self):
        return self.parent.doc

    @property
    def name(self):
        return self.attr

    def specified(self):
        return self.parent.has_key(self.attr)

    def getValue(self):
        if self.parent:
            if self.parent.tag.has_key(self.attr):
                return self.parent.tag[self.attr]

        return self._value

    def setValue(self, value):
        self._value = value

        if self.parent:
            self.parent.tag[self.attr] = value

    value = property(getValue, setValue)

class Element(Node):
    def __init__(self, doc, tag):
        Node.__init__(self, doc)

        self.tag = tag

    def __repr__(self):
        return "<Element %s at 0x%08X>" % (self.tag.name, id(self))

    def __eq__(self, other):
        return Node.__eq__(self, other) and hasattr(other, "tag") and self.tag == other.tag

    @property
    def nodeType(self):
        return Node.ELEMENT_NODE

    @property
    def nodeName(self):
        return self.tagName

    @property
    def nodeValue(self):
        return None

    @property
    def attributes(self):
        return NamedNodeMap(self)

    @property
    def parentNode(self):
        return self.tag.parent

    @property
    def childNodes(self):
        return NodeList(self.doc, self.tag.contents)

    @property
    def firstChild(self):
        return Node.wrap(self.doc, self.tag.contents[0]) if len(self.tag) > 0 else None

    @property
    def lastChild(self):
        return Node.wrap(self.doc, self.tag.contents[-1]) if len(self.tag) > 0 else None

    @property
    def nextSibling(self):
        return Node.wrap(self.doc, self.tag.nextSibling)

    @property
    def previousSibling(self):
        return Node.wrap(self.doc, self.tag.previousSibling)

    def checkChild(self, child):
        if not isinstance(child, Node):
            raise DOMException(DOMException.HIERARCHY_REQUEST_ERR)

    def findChild(self, child):
        try:
            return self.tag.contents.index(child.tag)
        except ValueError:
            return -1

    def insertBefore(self, newChild, refChild):
        self.checkChild(newChild)
        self.checkChild(refChild)

        index = self.findChild(refChild)

        if index < 0:
            self.tag.append(newChild.tag)
        else:
            self.tag.insert(index, newChild.tag)

        return newChild

    def replaceChild(self, newChild, oldChild):
        self.checkChild(newChild)
        self.checkChild(oldChild)

        index = self.findChild(oldChild)

        if index < 0:
            raise DOMException(DOMException.NOT_FOUND_ERR)

        self.tag.contents[index] = newChild.tag

        return oldChild

    def removeChild(self, oldChild):
        self.checkChild(oldChild)

        self.tag.contents.remove(oldChild.tag)

        return oldChild

    def appendChild(self, newChild):
        if newChild:
            if isinstance(newChild, Text):
                self.tag.append(str(newChild))
            else:
                self.checkChild(newChild)

                self.tag.append(newChild.tag)

        return newChild

    def hasChildNodes(self):
        return len(self.tag.contents) > 0

    @property
    def tagName(self):
        return self.tag.name

    def getAttribute(self, name):
        return self.tag[name] if self.tag.has_key(name) else ""

    def setAttribute(self, name, value):
        self.tag[name] = value

    def removeAttribute(self, name):
        del self.tag[name]

    def getAttributeNode(self, name):
        return Attr(self, name) if self.tag.has_key(name) else None

    def setAttributeNode(self, attr):
        self.tag[attr.name] = attr.value

    def removeAttributeNode(self, attr):
        del self.tag[attr.name]

    def getElementsByTagName(self, name):
        return NodeList(self.doc, self.tag.findAll(name))

    def normalize(self):
        pass

class CharacterData(Node):
    def __init__(self, doc, tag):
        Node.__init__(self, doc)

        self.tag = tag

    def __str__(self):
        return str(self.tag)

    def getData(self):
        return unicode(self.tag)

    def setData(self, data):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

    data = property(getData, setData)

    @property
    def length(self):
        return len(self.tag)

    def substringData(self, offset, count):
        return self.tag[offset:offset+count]

    def appendData(self, arg):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

    def insertData(self, offset, arg):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

    def deleteData(self, offset, count):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

    def replaceData(self, offset, count, arg):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

class Text(CharacterData):
    def __repr__(self):
        return "<Text '%s' at 0x%08X>" % (self.tag, id(self))

    def splitText(self, offset):
        raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)

class CDATASection(Text):
    def __repr__(self):
        return "<CDATA '%s' at 0x%08X>" % (self.tag, id(self))

class Comment(CharacterData):
    pass

class DocumentFragment(Node):
    pass

class DocumentType(Node):
    RE_DOCTYPE = re.compile("^DOCTYPE (\w+)", re.M + re.S)

    def __init__(self, doc, tag):
        Node.__init__(self, doc)

        self.parse(tag)

    def parse(self, text):
        m = self.RE_DOCTYPE.match(text)

        self._name = m.group(1) if m else ""

    @property
    def name(self):
        return self._name

    @property
    def entities(self):
        raise NotImplementedError()

    @property
    def notations(self):
        raise NotImplementedError()

class Notation(Node):
    @property
    def publicId(self):
        pass

    @property
    def systemId(self):
        pass

class Entity(Node):
    @property
    def publicId(self):
        pass

    @property
    def systemId(self):
        pass

    @property
    def notationName(self):
        pass

class EntityReference(Node):
    def __init__(self, doc, name):
        Node.__init__(self, doc)

        self.name = name

    def nodeName(self):
        return self.name

class ProcessingInstruction(Node):
    def __init__(self, doc, target, data):
        self._target = target
        self.data = data

    @property
    def target(self):
        return self._target

class Document(Node):
    @property
    def nodeType(self):
        return Node.DOCUMENT_NODE

    @property
    def nodeName(self):
        return "#document"

    @property
    def nodeValue(self):
        return None

    @property
    def childNodes(self):
        return NodeList(self.doc, self.doc.contents)

    @property
    def doctype(self):
        for tag in self.doc:
            if isinstance(tag, BeautifulSoup.Declaration) and tag.startswith("DOCTYPE"):
                return DocumentType(self.doc, tag)

        return None

    @property
    def implementation(self):
        return self

    @property
    def documentElement(self):
        return Element(self, self.doc.find('html'))

    onCreateElement = None

    def createElement(self, tagname):
        element = DOMImplementation.createHTMLElement(self.doc, BeautifulSoup.Tag(self.doc, tagname))

        if self.onCreateElement:
            self.onCreateElement(element)

        return element

    def createDocumentFragment(self):
        return DocumentFragment(self)

    def createTextNode(self, data):
        return Text(self, BeautifulSoup.NavigableString(data))

    def createComment(self, data):
        return Comment(self, data)

    def createCDATASection(self, data):
        return CDATASection(self, data)

    def createProcessingInstruction(self, target, data):
        return ProcessingInstruction(self, target, data)

    def createAttribute(self, name):
        return Attr(None, name)

    def createEntityReference(self, name):
        return EntityReference(self, name)

    def getElementsByTagName(self, tagname):
        return NodeList(self.doc, self.doc.findAll(tagname.lower()))

def attr_property(name, attrtype=str, readonly=False, default=None):
    def getter(self):
        return attrtype(self.tag[name]) if self.tag.has_key(name) else default

    def setter(self, value):
        self.tag[name] = attrtype(value)

    return property(getter) if readonly else property(getter, setter)

def text_property(readonly=False):
    def getter(self):
        return self.tag.string

    def setter(self, text):
        if self.tag.string:
            self.tag.contents[0] = BeautifulSoup.NavigableString(text)
        else:
            self.tag.append(text)

        self.tag.string = self.tag.contents[0]

    return property(getter) if readonly else property(getter, setter)

class HTMLCollection(PyV8.JSClass):
    def __init__(self, doc, nodes):
        self.doc = doc
        self.nodes = nodes

    def __len__(self):
        return self.length

    def __getitem__(self, key):
        try:
            return self.item(int(key))
        except TypeError:
            return self.namedItem(str(key))

    @property
    def length(self):
        return len(self.nodes)

    def item(self, index):
        node = self.nodes[index]

        return DOMImplementation.createHTMLElement(self.doc, node) if node else None

    def namedItem(self, name):
        for node in self.nodes:
            if node.nodeName == name:
                return DOMImplementation.createHTMLElement(self.doc, node) if node else None

        return None

class CSSStyleDeclaration(object):
    def __init__(self, style):
        self.props = dict([prop.strip().split(': ') for prop in style.split(';') if prop])

        for k, v in self.props.items():
            if v and v[0] == v[-1] and v[0] in ['"', "'"]:
                self.props[k] = v[1:-1]

    @property
    def cssText(self):
        return '; '.join(["%s: %s" % (k, v) for k, v in self.props.items()])

    def getPropertyValue(self, name):
        return self.props.get(name, '')

    def removeProperty(self, name):
        v = self.props.get(name, '')

        if v:
            del self.props[name]

        return v

    @property
    def length(self):
        return len(self.props)

    def item(self, index):
        if type(index) == str:
            return self.props.get(index, '')

        if index < 0 or index >= len(self.props):
            return ''

        return self.props[self.props.keys()[index]]

    def __getattr__(self, name):
        if hasattr(object, name):
            return object.__getattribute__(self, name)
        else:
            return object.__getattribute__(self, 'props').get(name, '')

    def __setattr__(self, name, value):
        if name == 'props':
            object.__setattr__(self, name, value)
        else:
            object.__getattribute__(self, 'props')[name] = value

class ElementCSSInlineStyle(object):
    @property
    def style(self):
        return CSSStyleDeclaration(self.tag['style'] if self.tag.has_key('style') else '')

class HTMLElement(Element, ElementCSSInlineStyle):
    id = attr_property("id")
    title = attr_property("title")
    lang = attr_property("lang")
    dir = attr_property("dir")
    className = attr_property("class")

class HTMLHtmlElement(HTMLElement):
    version = attr_property("version")

class HTMLHeadElement(HTMLElement):
    profile = attr_property("profile")

class HTMLLinkElement(HTMLElement):
    disabled = False

    charset = attr_property("charset")
    href = attr_property("href")
    hreflang = attr_property("hreflang")
    media = attr_property("media")
    rel = attr_property("rel")
    rev = attr_property("rev")
    target = attr_property("target")
    type = attr_property("type")

class HTMLTitleElement(HTMLElement):
    text = text_property()

class HTMLMetaElement(HTMLElement):
    content = attr_property("content")
    httpEquiv = attr_property("http-equiv")
    name = attr_property("name")
    scheme = attr_property("scheme")

class HTMLBaseElement(HTMLElement):
    href = attr_property("href")
    target = attr_property("target")

class HTMLIsIndexElement(HTMLElement):
    form = None
    prompt = attr_property("prompt")

class HTMLStyleElement(HTMLElement):
    disabled = False

    media = attr_property("media")
    type = attr_property("type")

class HTMLBodyElement(HTMLElement):
    background = attr_property("background")
    bgColor = attr_property("bgcolor")
    link = attr_property("link")
    aLink = attr_property("alink")
    vLink = attr_property("vlink")
    text = attr_property("text")

class HTMLFormElement(HTMLElement):
    @property
    def elements(self):
        raise NotImplementedError()

    @property
    def length(self):
        raise NotImplementedError()

    name = attr_property("name")
    acceptCharset = attr_property("accept-charset", default="UNKNOWN")
    action = attr_property("action")
    enctype = attr_property("enctype", default="application/x-www-form-urlencoded")
    method = attr_property("method", default="get")
    target = attr_property("target")

    def submit(self):
        raise NotImplementedError()

    def reset(self):
        raise NotImplementedError()

class HTMLSelectElement(HTMLElement):
    @property
    def type(self):
        raise NotImplementedError()

    selectedIndex = 0
    value = None

    @property
    def length(self):
        raise NotImplementedError()

    @property
    def form(self):
        raise NotImplementedError()

    @property
    def options(self):
        raise NotImplementedError()

    disabled = attr_property("disabled", bool)
    multiple = attr_property("multiple", bool)
    name = attr_property("name")
    size = attr_property("size", long)
    tabIndex = attr_property("tabindex", long)

    def add(self, element, before):
        raise NotImplementedError()

    def remove(self, index):
        raise NotImplementedError()

    def blur(self):
        raise NotImplementedError()

    def focus(self):
        raise NotImplementedError()

class HTMLOptGroupElement(HTMLElement):
    disabled = attr_property("disabled", bool)
    label = attr_property("label")

class HTMLOptionElement(HTMLElement):
    @property
    def form(self):
        raise NotImplementedError()

    defaultSelected = attr_property("selected", bool)
    text = text_property(readonly=True)
    index = attr_property("index", long)
    disabled = attr_property("disabled", bool)
    label = attr_property("label")
    selected = False
    value = attr_property("value")

class HTMLInputElement(HTMLElement):
    defaultValue = attr_property("value")
    defaultChecked = attr_property("checked")

    @property
    def form(self):
        raise NotImplementedError()

    accept = attr_property("accept")
    accessKey = attr_property("accesskey")
    align = attr_property("align")
    alt = attr_property("alt")
    checked = attr_property("checked", bool)
    disabled = attr_property("disabled", bool)
    maxLength = attr_property("maxlength", long, default=sys.maxint)
    name = attr_property("name")
    readOnly = attr_property("readonly", bool)
    size = attr_property("size")
    src = attr_property("src")
    tabIndex = attr_property("tabindex", long)
    type = attr_property("type", readonly=True, default="text")
    useMap = attr_property("usermap")

    @abstractmethod
    def getValue(self):
        pass

    @abstractmethod
    def setValue(self, value):
        pass

    value = property(getValue, setValue)

    def blur(self):
        pass

    def focus(self):
        pass

    def select(self):
        pass

    def click(self):
        pass

class HTMLTextAreaElement(HTMLElement):
    defaultValue = None

    @property
    def form(self):
        pass

    accessKey = attr_property("accesskey")
    cols = attr_property("cols", long)
    disabled = attr_property("disabled", bool)
    name = attr_property("name")
    readOnly = attr_property("readonly", bool)
    rows = attr_property("rows", long)
    tabIndex = attr_property("tabindex", long)
    value = text_property()

    @property
    def type(self):
        return "textarea"

class HTMLButtonElement(HTMLElement):
    @property
    def form(self):
        pass

    accessKey = attr_property("accesskey")
    disabled = attr_property("disabled", bool)
    name = attr_property("name")
    tabIndex = attr_property("tabindex", long)
    type = attr_property("type")
    value = attr_property("value")

class HTMLAppletElement(HTMLElement):
    align = attr_property("align")
    alt = attr_property("alt")
    archive = attr_property("archive")
    code = attr_property("code")
    codeBase = attr_property("codebase")
    height = attr_property("height")
    hspace = attr_property("hspace")
    name = attr_property("name")
    object = attr_property("object")
    vspace = attr_property("vspace")
    width = attr_property("width")

class HTMLImageElement(HTMLElement):
    align = attr_property("align")
    alt = attr_property("alt")
    border = attr_property("border")
    height = attr_property("height")
    hspace = attr_property("hspace")
    isMap = attr_property("ismap")
    longDesc = attr_property("longdesc")
    lowSrc = attr_property("lowsrc")
    name = attr_property("name")
    src = attr_property("src")
    useMap = attr_property("usemap")
    vspace = attr_property("vspace")
    width = attr_property("width")

class HTMLScriptElement(HTMLElement):
    text = text_property()
    htmlFor = None
    event = None
    charset = attr_property("charset")
    defer = attr_property("defer", bool)
    src = attr_property("src")
    type = attr_property("type")

class HTMLFrameSetElement(HTMLElement):
    cols = attr_property("cols")
    rows = attr_property("rows")

class HTMLFrameElement(HTMLElement):
    frameBorder = attr_property("frameborder")
    longDesc = attr_property("longdesc")
    marginHeight = attr_property("marginheight")
    marginWidth = attr_property("marginwidth")
    name = attr_property("name")
    noResize = attr_property("noresize", bool)
    scrolling = attr_property("scrolling")
    src = attr_property("src")

class HTMLIFrameElement(HTMLElement):
    align = attr_property("align")
    frameBorder = attr_property("frameborder")
    height = attr_property("height")
    longDesc = attr_property("longdesc")
    marginHeight = attr_property("marginheight")
    marginWidth = attr_property("marginwidth")
    name = attr_property("name")
    scrolling = attr_property("scrolling")
    src = attr_property("src")
    width = attr_property("width")

class HTMLCanvasElement(HTMLElement):
    width = attr_property("width", default=300)
    height = attr_property("height", default=150)

    def getContext(self, id):
        return self.tag.canvas if id == '2d' else None

def xpath_property(xpath, readonly=False):
    RE_INDEXED = re.compile("(\w+)\[(\d+)\]")

    parts = xpath.split('/')

    def getChildren(tag, parts, recursive=False):
        if len(parts) == 0:
            return [tag]

        part = parts[0]

        if part == '':
            return getChildren(tag, parts[1:], True)

        if part == 'text()':
            return [tag.string]

        m = RE_INDEXED.match(part)

        if m:
            name = m.group(1)
            idx = int(m.group(2))

            return [tag.findAll(name, recursive=recursive)[idx-1]]

        children = []

        for child in tag.findAll(part, recursive=recursive):
            children += getChildren(child, parts[1:])

        return children

    def getter(self):
        children = []

        if xpath.startswith("//"):
            for child in self.doc.findAll(parts[2]):
                children += getChildren(child, parts[3:])
        elif xpath.startswith("/"):
            children += getChildren(self.doc, parts[1:])
        else:
            children += getChildren(self.doc, parts)

        if parts[-1] == 'text()':
            return "".join(children)

        if RE_INDEXED.match(parts[-1]):
            return DOMImplementation.createHTMLElement(self.doc, children[0]) if len(children) > 0 else None

        return HTMLCollection(self.doc, children)

    def setter(self, value):
        tag = self.doc

        for part in parts:
            if part == '':
                continue
            elif part == 'text()':
                if tag.string:
                    tag.contents[0] = BeautifulSoup.NavigableString(value)
                else:
                    tag.append(value)

                return
            else:
                child = tag.find(part)

                if not child:
                    child = BeautifulSoup.Tag(self.doc, part)

                    tag.append(child)

                tag = child

        tag.append(value)

    return property(getter) if readonly else property(getter, setter)

class HTMLDocument(Document):
    title = xpath_property("/html/head/title/text()")

    @property
    def referrer(self):
        raise NotImplementedError()

    @property
    def domain(self):
        raise NotImplementedError()

    @property
    def URL(self):
        raise NotImplementedError()

    body = xpath_property("/html/body[1]", readonly=True)

    images = xpath_property("//img", readonly=True)
    applets = xpath_property("//applet", readonly=True)
    links = xpath_property("//a", readonly=True)
    forms = xpath_property("//form", readonly=True)

    @property
    def anchors(self):
        raise NotImplementedError()

    cookie = ""

    def open(self):        
		print "\t\t--> Website attempted to open output stream"

    def close(self):
        print "\t\t--> Website attempted to close output stream"

    onDocumentWrite = None

    def write(self, html):
        #tag = BeautifulSoup.BeautifulSoup(html)
		#
        #if self.onDocumentWrite:
        #    self.onDocumentWrite(DOMImplementation.createHTMLElement(self.doc, tag))
		#
        #self.body.tag.append(tag)
		print "\t\t--> Website attempted write: " + html
    
    def writeln(self, text):
        self.write(text + "\n")

    def getElementById(self, elementId):
        tag = self.doc.find(id=elementId)
        return DOMImplementation.createHTMLElement(self.doc, tag) if tag else None

class DOMImplementation(HTMLDocument):
    def hasFeature(self, feature, version):
        return feature == "HTML" and version == "1.0"

    TAGS = {
        "html" : HTMLHtmlElement,
        "head" : HTMLHeadElement,
        "link" : HTMLLinkElement,
        "title" : HTMLTitleElement,
        "meta" : HTMLMetaElement,
        "base" : HTMLBaseElement,
        "isindex" : HTMLIsIndexElement,
        "style" : HTMLStyleElement,
        "body" : HTMLBodyElement,
        "form" : HTMLFormElement,
        "select" : HTMLSelectElement,
        "optgroup" : HTMLOptGroupElement,
        "option" : HTMLOptionElement,
        "input" : HTMLInputElement,
        "textarea" : HTMLTextAreaElement,
        "button" : HTMLButtonElement,
        "applet" : HTMLAppletElement,
        "img" : HTMLImageElement,
        "script" : HTMLScriptElement,
        "frameset" : HTMLFrameSetElement,
        "frame" : HTMLFrameElement,
        "iframe" : HTMLIFrameElement,
        "canvas" : HTMLCanvasElement,
    }

    @staticmethod
    def createHTMLElement(doc, tag):
        if DOMImplementation.TAGS.has_key(tag.name.lower()):
            return DOMImplementation.TAGS[tag.name.lower()](doc, tag)
        else:
            return HTMLElement(doc, tag)

def getDOMImplementation(dom = None):
    return DOMImplementation(dom if dom else BeautifulSoup.BeautifulSoup())

def parseString(html):
    return DOMImplementation(BeautifulSoup.BeautifulSoup(html))

def parse(file):
    if isinstance(file, StringTypes):
        with open(file, 'r') as f:
            return parseString(f.read())

    return parseString(file.read())
